﻿Namespace Tools
  Public Class EnumTree
    Private mEnumType As Type
    Private mRoot As Node
    Private mValue As ULong

#Region "Constructors"
    Public Sub New(ByVal enumType As Type)
      mEnumType = enumType
      mRoot = Nothing

      BuildTree()
    End Sub
#End Region

#Region "Properties"
    Public ReadOnly Property RootNode() As Node
      Get
        Return mRoot
      End Get
    End Property

    Public Property Value() As ULong
      Get
        Return mValue
      End Get
      Set(ByVal value As ULong)
        If (value And mRoot.Value) = value Then mValue = value
      End Set
    End Property
#End Region

#Region "Methods"
    Private Sub BuildTree()
      Dim nodesByComplexity As SortedList(Of ULong, SortedList(Of ULong, Node))
      Dim enumValues As Array
      Dim nodeMajor As Node
      Dim nodeMinor As Node
      Dim idxNode As Integer
      Dim idxMajor As Integer
      Dim idxMinor As Integer
      Dim count As Integer

      ' ========================================================================
      ' === Pre-Process                                                      ===
      ' ========================================================================
      ' Initialize data
      mRoot = Nothing
      nodesByComplexity = New SortedList(Of ULong, SortedList(Of ULong, Node))

      ' Put all the enum entries into a list grouping nodes by complexity level
      enumValues = [Enum].GetValues(mEnumType)
      For idxMajor = 0 To enumValues.Length - 1
        If enumValues(idxMajor) <> 0 Then
          nodeMajor = New Node()
          nodeMajor.Value = enumValues(idxMajor)
          nodeMajor.Caption = enumValues(idxMajor).ToString()

          If nodesByComplexity.ContainsKey(nodeMajor.ComplexityLevel) = False Then
            nodesByComplexity.Add(nodeMajor.ComplexityLevel, New SortedList(Of ULong, Node))
          End If
          nodesByComplexity(nodeMajor.ComplexityLevel).Add(nodeMajor.Value, nodeMajor)
        Else
          ' Any enum entry with value "0" is currently not managed since it 
          ' represents no entry selected
        End If
      Next

      ' ========================================================================
      ' === Process                                                          ===
      ' ========================================================================
      ' Link the nodes together, from the lower complexity level to the highest
      For idxMajor = 1 To nodesByComplexity.Values.Count - 1
        For Each nodeMajor In nodesByComplexity.Values(idxMajor).Values
          For idxMinor = 0 To idxMajor - 1
            idxNode = 0
            While idxNode < nodesByComplexity.Values(idxMinor).Values.Count
              nodeMinor = nodesByComplexity.Values(idxMinor).Values(idxNode)
              If (nodeMinor.Parent Is Nothing) AndAlso ((nodeMinor.Value And nodeMajor.Value) = nodeMinor.Value) Then
                nodeMinor.Parent = nodeMajor
                nodeMajor.Children.Add(nodeMinor)
                nodesByComplexity.Values(idxMinor).RemoveAt(idxNode)
              Else
                idxNode += 1
              End If
            End While
          Next
        Next
      Next

      ' ========================================================================
      ' === Post-Process                                                     ===
      ' ========================================================================
      idxMajor = 0
      nodeMajor = Nothing
      While idxMajor < nodesByComplexity.Values.Count
        If nodesByComplexity.Values(idxMajor).Count = 0 Then
          nodesByComplexity.RemoveAt(idxMajor)
        Else
          ' compute the number of resulting nodes
          count += nodesByComplexity.Values(idxMajor).Count
          ' Save the latest node, in case there is one node remaining
          nodeMajor = nodesByComplexity.Values(idxMajor).Values(nodesByComplexity.Values(idxMajor).Values.Count - 1)

          idxMajor += 1
        End If
      End While

      If count = 1 Then
        ' One resulting node: ok
        mRoot = nodeMajor
      Else
        ' Many resulting nodes: not ok, but don't throw an error
        Stop
      End If
    End Sub

    Function ParseValue(ByVal value As ULong) As List(Of String)
      Dim rc As List(Of String)

      rc = New List(Of String)
      ParseValue(mRoot, value, rc)
      Return rc
    End Function

    Private Sub ParseValue(ByVal rootNode As Node, ByVal value As ULong, ByVal result As List(Of String))
      If (rootNode.Value And value) = rootNode.Value Then
        result.Add(rootNode.Caption)
      Else
        For Each item As Node In rootNode.Children
          ParseValue(item, value, result)
        Next
      End If
    End Sub
    'Public Function GetValues() As List(Of String)
    '  Dim rc As New List(Of String)
    '  Dim enumValues As Array
    '  Dim i As Integer

    '  enumValues = [Enum].GetValues(mEnumType)
    '  For i = 0 To enumValues.Length - 1
    '    If (enumValues(i) And mValue) <> 0 Then
    '      rc.Add(enumValues(i).ToString)
    '    End If
    '  Next

    '  Return rc
    'End Function

    'Public Sub SetValues(ByVal values As List(Of String))
    '  Dim rc As ULong
    '  Dim enumValues As Array
    '  Dim i As Integer

    '  rc = 0
    '  enumValues = [Enum].GetValues(mEnumType)
    '  For i = 0 To enumValues.Length - 1
    '    If values.Contains(enumValues(i).ToString()) = True Then
    '      rc = rc Or enumValues(i)
    '    End If
    '  Next

    '  mValue = rc
    'End Sub

    'Public Sub SetValues(ByVal values As ULong)
    '  Dim rc As ULong
    '  Dim enumValues As Array
    '  Dim i As Integer

    '  rc = 0
    '  enumValues = [Enum].GetValues(mEnumType)
    '  For i = 0 To enumValues.Length - 1
    '    If (values And enumValues(i)) = enumValues(i) Then
    '      rc = rc Or enumValues(i)
    '    End If
    '  Next

    '  mValue = rc
    'End Sub
#End Region

#Region "Class Node"
    Public Class Node
#Region "Private Members"
      Private mParent As Node
      Private mChildren As List(Of Node)
      Private mValue As ULong
      Private mCaption As String
      Private mComplexityLevel As Integer
#End Region

#Region "Constructors"
      Public Sub New()
        mParent = Nothing
        mChildren = New List(Of Node)
      End Sub
#End Region

#Region "Properties"
      Public Property Parent() As Node
        Get
          Return mParent
        End Get
        Set(ByVal value As Node)
          mParent = value
        End Set
      End Property

      Public ReadOnly Property Children() As List(Of Node)
        Get
          Return mChildren
        End Get
      End Property

      Public Property Value() As ULong
        Get
          Return mValue
        End Get
        Set(ByVal value As ULong)
          Dim i As Integer

          mValue = value
          mComplexityLevel = 0

          While (2 ^ i <= value)
            If ((value And 2 ^ i) = 2 ^ i) Then mComplexityLevel += 1
            i += 1
          End While
        End Set
      End Property

      Public Property Caption() As String
        Get
          Return mCaption
        End Get
        Set(ByVal value As String)
          mCaption = value
        End Set
      End Property

      Public ReadOnly Property ComplexityLevel() As Integer
        Get
          Return mComplexityLevel
        End Get
      End Property
#End Region
    End Class
#End Region
  End Class
End Namespace